﻿using ICSharpCode.TextEditor.Util;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Threading.Tasks;

namespace ICSharpCode.TextEditor.Document
{
    public class GuidelinesManager : IGuidelinesManager
    {
        int offsetWidth = 3;

        bool redrawable = false;
        bool linesIsUpdating = false;        
        bool threedSynchCompleted = false;

        char openedChar = '{';
        char closedChar = '}';

        IDocument document = null;        

        LineManager lineManager = null;        

        ThreadSafeList<DrawLine> guidelines = new ThreadSafeList<DrawLine>();

        Stack<Guideline> GuidelineStack = new Stack<Guideline>();

        public bool ShowGuidelines
        {
            get
            {
                if (document == null)
                    return false;
                return document.TextEditorProperties.ShowGuidelines;
            }
        }

        public bool Redrawable
        {
            get { return redrawable; }
            set { redrawable = value; }
        }

        private bool LineIsDrawable(Rectangle rect,Point start,Point end)
        {
            int top = rect.Top;
            int bottom = rect.Bottom;

            if (start.Y < top && end.Y < top)
            {
                return false;
            }
            if (start.Y > bottom && end.Y > bottom)
            {
                return false;
            }

            return true;
        }

        private char GetCharAtLineSegment(string text)
        {
            char result = '\0';

            if (!string.IsNullOrEmpty(text))
            {
                int index1 = text.IndexOf(openedChar);
                int index2 = text.IndexOf(closedChar);
                if (index1 != -1 && index2 != -1)
                    return result;

                if (index1 == -1 && index2 == -1)
                    return result;

                if (text.IndexOf("//") != -1)
                    text = text.Substring(0, text.IndexOf("//"));

                if (string.IsNullOrEmpty(text))
                    return result;

                char[] chrs = text.Trim().ToCharArray();
                for (int j = 0; j < 1; j++)
                {
                    if (chrs[j] == closedChar)
                    {
                        result = closedChar;
                        break;
                    }
                }
                if (result != closedChar)
                {
                    for (int j = chrs.Length - 1; j >= 0; j--)
                    {
                        if (chrs[j] != ' ')
                        {
                            result = chrs[j];
                            break;
                        }
                    }
                }
            }

            return result;
        }

        private Point GetScreenPosition(TextArea textArea, int line, int column, Point offset)
        {
            //Point virtualTop = textArea.TextView.TextArea.VirtualTop;
            Point virtualTop = new Point(textArea.VirtualTop.X, textArea.VScrollBar.Value);
            int xpos = textArea.TextView.GetDrawingXPos(line, column);
            return new Point(textArea.TextView.DrawingPosition.X + xpos + offset.X,
                             textArea.TextView.DrawingPosition.Y
                             + (textArea.Document.GetVisibleLine(line)) * textArea.TextView.FontHeight
                             - virtualTop.Y + offset.Y);
        }

        private void UpdateGuidelines(TextArea textArea)
        {
            linesIsUpdating = true;

            //document = textArea.Document;

            if (!document.TextEditorProperties.ShowGuidelines)
                return;

            GuidelineStack.Clear();

            //创建临时副本
            ThreadSafeList<DrawLine> _tempCopyList = new ThreadSafeList<DrawLine>();

            if (!document.TextEditorProperties.ShowGuidelines)
                return;             

            for (int i = 0; i < lineManager.TotalNumberOfLines; i++)
            {
                char c;
                int offs, end = document.TextLength;
                LineSegment seg = document.GetLineSegment(i);

                if (!linesIsUpdating)
                    break;

                offs = seg.Offset;
                for (; offs < end && ((c = document.GetCharAt(offs)) == ' ' || c == '\t'); offs++)
                {
                    //offs 增加
                }

                int spaceCount = offs - seg.Offset;

                // now offs points to the first non-whitespace char on the line

                //int visibleLineNum = document.GetVisibleLine(i);
                //if (visibleLineNum != i)
                //    continue;

                char dott = GetCharAtLineSegment(
                    document.GetText(offs, seg.Length - spaceCount));

                //char dott = GetCharAtLineSegment(
                //   document.GetText(seg));

                if (!linesIsUpdating)
                    break;

                Guideline guideline = null;

                if (dott == openedChar)
                {
                    guideline = new Guideline();
                    guideline.LineNumber = i;
                    guideline.Location = document.OffsetToPosition(offs);
                    GuidelineStack.Push(guideline);
                }
                else if (dott == closedChar)
                {
                    if (GuidelineStack.Count > 0)
                    {
                        TextLocation location = document.OffsetToPosition(offs);
                        guideline = GuidelineStack.Pop();
                        if (location != null &&
                            guideline != null &&
                            guideline.Location.Line < (location.Line - 1) &&
                            guideline.Location.Column == location.Column)
                        {
                            //添加辅助线                                
                            DrawLine drawLine = new DrawLine(guideline.Location, location);
                            drawLine.StartPoint = GetScreenPosition(textArea, guideline.Location.Line, guideline.Location.Column, new Point(offsetWidth, textArea.TextView.FontHeight + 2));
                            drawLine.EndPoint = GetScreenPosition(textArea, location.Line, location.Column, new Point(offsetWidth, -2));

                            //int visibleLineNum = document.GetVisibleLine(guideline.LineNumber);

                            //二次验证是否是在一条线上
                            //visibleLineNum == guideline.LineNumber &&
                            if (drawLine.StartPoint.X == drawLine.EndPoint.X)
                            {
                                _tempCopyList.Add(drawLine);
                            }

                        }
                    }
                }
            }

            lock (guidelines.SynchRoot)
            {
                guidelines = _tempCopyList.CloneToList<DrawLine>();
                _tempCopyList.Dispose();
            }

            linesIsUpdating = false;           
        }

        private void UpdateDrawLines(TextArea textArea)
        {
            if (!document.TextEditorProperties.ShowGuidelines)
                return;
            if (!threedSynchCompleted)
                return;
            if (textArea == null)
                return;

            lock (guidelines.SynchRoot)
            {
                if (!document.TextEditorProperties.ShowGuidelines)
                    return;               
                if (!threedSynchCompleted)
                    return;
                if (textArea == null)
                    return;

                linesIsUpdating = true;

                foreach (DrawLine line in this.guidelines)
                {
                    if (!threedSynchCompleted || !linesIsUpdating)
                        break;
                    line.StartPoint = GetScreenPosition(textArea, line.StartLocation.Line, line.StartLocation.Column, new Point(offsetWidth, textArea.TextView.FontHeight + 2));
                    if (!threedSynchCompleted || !linesIsUpdating)
                        break;
                    line.EndPoint = GetScreenPosition(textArea, line.EndLocation.Line, line.EndLocation.Column, new Point(offsetWidth, -2));
                }

                linesIsUpdating = false;
            }
        }

        private void TextAreaInvalidate(TextArea textArea)
        {
            if (redrawable)
                textArea.Invalidate();
            else
                textArea.Invalidate(textArea.TextView.DrawingPosition);
        }

        internal GuidelinesManager(IDocument document, LineManager lineManager)
        {
            this.document = document;
            this.lineManager = lineManager;
        }

        internal virtual void Render(Graphics g, TextArea textArea)
        {
            if (linesIsUpdating)
                return;         

            if (!document.TextEditorProperties.ShowGuidelines)
                return;

            Rectangle drawRectangle = textArea.TextView.DrawingPosition;

            using (var pen = new Pen(Brushes.Gray))
            {
                pen.DashStyle = DashStyle.DashDot;

                foreach (DrawLine line in guidelines)
                {
                    //if (drawRectangle.Contains(start) || drawRectangle.Contains(end))
                    //    line.Draw(g, start, end, pen);

                    if (document.TextEditorProperties.EnableFolding &&
                        !document.FoldingManager.IsLineVisibleFast(line.StartLocation.Line))
                        continue;

                    Point start = GetScreenPosition(textArea, line.StartLocation.Line, line.StartLocation.Column, new Point(offsetWidth, textArea.TextView.FontHeight + 2));
                    Point end = GetScreenPosition(textArea, line.EndLocation.Line, line.EndLocation.Column, new Point(offsetWidth, -2));

                    //在可见区域才绘制
                    if (LineIsDrawable(drawRectangle, start, end))
                    {
                        line.Draw(g, start, end, pen);
                    }

                }

            }
        }

        internal Task<bool> AsyncUpdateDrawLinesTask(TextArea textArea)
        {
            var tcs = new TaskCompletionSource<bool>();
            new Task(() =>
            {
                System.Threading.Thread.Sleep(15);
                UpdateDrawLines(textArea);
                tcs.SetResult(true);
            }).Start();
            return tcs.Task;
        }

        internal Task<bool> AsyncUpdateGuidelinesTask(TextArea textArea)
        {
            var tcs = new TaskCompletionSource<bool>();
            new Task(() =>
            {
                UpdateGuidelines(textArea);
                tcs.SetResult(true);
            }).Start();
            return tcs.Task;
        }

        public void Update(TextArea textArea)
        {
            guidelines = new ThreadSafeList<DrawLine>();

            if (!document.TextEditorProperties.ShowGuidelines)
                return;

            if (document.TextEditorProperties.SplitState &&
                document.TextEditorProperties.EnableFolding)
                return;

            if (textArea.Document.TotalNumberOfLines <= 0)
                return;

            if (textArea.TextView == null)
                return;

            threedSynchCompleted = false;

            if (!linesIsUpdating)
            {
                offsetWidth = textArea.TextView.CurlyBracketsWidth;

                UpdateGuidelines(textArea);

                if (textArea.Document.TotalNumberOfLines > 0)
                    TextAreaInvalidate(textArea);

            }

            threedSynchCompleted = true;

        }

        public void Invalidate(TextArea textArea)
        {
            bool result = false;
            if (document.TextEditorProperties.ShowGuidelines)
            {
                if (!threedSynchCompleted)
                    return;
                linesIsUpdating = false;
                
                //result = AsyncUpdateDrawLinesTask(textArea).Result;
                UpdateDrawLines(textArea);

                TextAreaInvalidate(textArea);
                //textArea.Invalidate(textArea.TextView.DrawingPosition);
                //textArea.Invalidate();
            }
        }

        public void Clear(TextArea textArea)
        {
            if (!document.TextEditorProperties.ShowGuidelines)
                return;
            threedSynchCompleted = false;
            guidelines.Clear();           
            guidelines = new ThreadSafeList<DrawLine>();
            textArea.Invalidate(textArea.TextView.DrawingPosition);
        }

    }
}
